package io.modifier.basic.emo.scheme.impl

import android.app.Activity
import android.app.Application
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import io.modifier.basic.emo.scheme.Scheme
import io.modifier.basic.emo.scheme.SchemeDef
import io.modifier.basic.emo.scheme.SchemeDefStorage
import io.modifier.basic.emo.scheme.SchemeHost
import io.modifier.basic.emo.scheme.SchemeParts
import io.modifier.basic.emo.scheme.SchemeTransaction
import io.modifier.basic.emo.scheme.SchemeTransactionFactory
import kotlin.reflect.KClass

class SchemeExecSingle(
    private val activity: Activity,
    private val storage: SchemeDefStorage,
    private val transitionProvider: (Int) -> SchemeTransitionProvider,
) : SchemeTransaction {

    override fun exec(schemeParts: SchemeParts): Boolean {
        val schemeDef = storage.find(schemeParts) ?: return false
        val scheme = schemeParts.parse(schemeDef)
        val transition = transitionProvider(schemeDef.transition)
        if (schemeDef.targetId != SchemeDef.COMPOSE_CLASS_SUFFIX) {
            val cls = Class.forName(schemeDef.targetId)
            val intent = Intent(activity, cls)
            scheme.args.forEach { (key, value) ->
                intent.putAny(key, value)
            }
            intent.putExtra(SchemeKeys.KEY_ORIGIN, schemeParts.origin)
            intent.handleSchemeFlags(scheme)
            activity.startActivity(intent)
            activity.overridePendingTransition(
                transition.activityEnterRes(),
                transition.activityExitRes()
            )
            return true
        } else {
            val routeValue = scheme.toComposeRouteValue()
            if (!scheme.forceNewHost() && activity is ComposeHostActivity && schemeDef.alternativeHosts.contains(activity::class)) {
                val navController = activity.navController ?: throw RuntimeException("Not call SchemeNavHost in method Content.")
                if (scheme.isMatchToCurrentHost(activity::class, activity.intent)) {
                    navController.navigate(routeValue)
                    return true
                }
            }
            val intent = scheme.createIntentForCompose(activity)
            if (intent != null) {
                activity.startActivity(intent.first)
                activity.overridePendingTransition(
                    transition.activityEnterRes(),
                    transition.activityExitRes()
                )
                return true
            }
        }
        return false
    }

    override fun finish(): Boolean {
        throw RuntimeException("should not be called.")
    }
}

class SchemeExecBatch(
    private val activity: Activity,
    private val storage: SchemeDefStorage,
    private val transitionProvider: (Int) -> SchemeTransitionProvider,
) : SchemeTransaction {

    private val intentList: MutableList<Intent> = ArrayList()
    private var buildingComposeIntent: BuildingComposeIntent? = null
    private var lastSchemeDef: SchemeDef? = null

    override fun exec(schemeParts: SchemeParts): Boolean {
        val schemeDef = storage.find(schemeParts) ?: return false
        val scheme = schemeParts.parse(schemeDef)
        lastSchemeDef = schemeDef
        if (schemeDef.targetId != SchemeDef.COMPOSE_CLASS_SUFFIX) {
            buildingComposeIntent?.let {
                intentList.add(it.build())
                buildingComposeIntent = null
            }
            val cls = Class.forName(schemeDef.targetId)
            val intent = Intent(activity, cls)
            scheme.args.forEach { (key, value) ->
                intent.putAny(key, value)
            }
            intent.handleSchemeFlags(scheme)
            intent.putExtra(SchemeKeys.KEY_ORIGIN, schemeParts.origin)
            intentList.add(intent)
            return true
        } else {
            val routeValue = scheme.toComposeRouteValue()
            if (intentList.isEmpty() && buildingComposeIntent == null &&
                !scheme.forceNewHost() &&
                activity is ComposeHostActivity &&
                schemeDef.alternativeHosts.contains(activity::class)
            ) {
                val navController = activity.navController ?: throw RuntimeException("Not call SchemeNavHost in method Content.")
                if (scheme.isMatchToCurrentHost(activity::class, activity.intent)) {
                    navController.navigate(routeValue)
                    return true
                }
            }
            val buildingIntent = buildingComposeIntent
            if (buildingIntent != null && schemeDef.alternativeHosts.contains(buildingIntent.activityCls)) {
                if (scheme.isMatchToCurrentHost(buildingIntent.activityCls, buildingIntent.intent)) {
                    buildingIntent.composeRoutes.add(routeValue)
                    return true
                }
            }
            buildingComposeIntent?.let {
                intentList.add(it.build())
                buildingComposeIntent = null
            }
            val intent = scheme.createIntentForCompose(activity)
            if (intent != null) {
                buildingComposeIntent = BuildingComposeIntent(intent.second, intent.first)
                return true
            }
        }
        return false
    }

    override fun finish(): Boolean {
        buildingComposeIntent?.let {
            intentList.add(it.build())
            buildingComposeIntent = null
        }
        if (intentList.isNotEmpty()) {
            activity.startActivities(intentList.toTypedArray())
            lastSchemeDef?.apply {
                val transition = transitionProvider(transition)
                activity.overridePendingTransition(transition.activityEnterRes(), transition.activityExitRes())
            }
        }
        return true
    }

    private class BuildingComposeIntent(
        val activityCls: KClass<*>,
        val intent: Intent,
        val composeRoutes: MutableList<String> = mutableListOf(),
    ) {
        fun build(): Intent {
            return intent.apply {
                if (composeRoutes.isNotEmpty()) {
                    putExtra(SchemeKeys.KEY_BATCH_SCHEME_LIST, composeRoutes.toTypedArray())
                }
            }
        }
    }
}

private fun Intent.handleSchemeFlags(scheme: Scheme) {
    val flag = scheme.getIntentFlag()
    if (flag > 0) {
        flags = flag
    }
}

private fun Intent.putAny(key: String, value: Any) {
    when (value) {
        is Boolean -> putExtra(key, value)
        is Int -> putExtra(key, value)
        is Long -> putExtra(key, value)
        is Float -> putExtra(key, value)
        is String -> putExtra(key, value)
        else -> throw RuntimeException("Not support type(${value::class.java} for $key")
    }
}

private fun Bundle.putAny(key: String, value: Any) {
    when (value) {
        is Boolean -> putBoolean(key, value)
        is Int -> putInt(key, value)
        is Long -> putLong(key, value)
        is Float -> putFloat(key, value)
        is String -> putString(key, value)
        else -> throw RuntimeException("Not support type(${value::class.java} for $key")
    }
}

private fun Scheme.createIntentForCompose(activity: Activity): Pair<Intent, KClass<*>>? {
    for (cls in def.alternativeHosts) {
        val intent = Intent(activity, cls.java)
        val schemeHost = cls.java.getAnnotation(SchemeHost::class.java)
        val matched = schemeHost == null ||
                schemeHost.requiredArgs.all { name ->
                    args.entries.find { it.key == name }?.also { intent.putAny(it.key, it.value) } != null
                }
        if (matched) {
            intent.putExtra(SchemeKeys.KEY_START_DESTINATION, def.toComposeRouteDefine())
            intent.putExtra(
                SchemeKeys.KEY_START_ARGUMENTS,
                Bundle().apply {
                    args.forEach { (key, value) ->
                        if (def.args.find { it.name == key }?.special != true) {
                            putAny(key, value)
                        }
                    }
                    putString(SchemeKeys.KEY_ORIGIN, Uri.encode(origin))
                }
            )
            intent.handleSchemeFlags(this)
            return intent to cls
        }
    }
    return null
}

private fun Scheme.isMatchToCurrentHost(host: KClass<*>, intent: Intent): Boolean {
    val schemeHost = host.java.getAnnotation(SchemeHost::class.java)
    return schemeHost == null || schemeHost.requiredArgs.asSequence().all {
        val value = args[it] ?: return@all false
        when (value) {
            is Boolean -> intent.getBooleanExtra(it, false) == value
            is Int -> intent.getIntExtra(it, 0) == value
            is Long -> intent.getLongExtra(it, 0) == value
            is Float -> intent.getFloatExtra(it, 0.0f) == value
            else -> intent.getStringExtra(it) == value
        }
    }
}

class AndroidSchemeExecTransactionFactory(
    val application: Application,
    val transitionProvider: (Int) -> SchemeTransitionProvider,
) : SchemeTransactionFactory {

    private var currentActivity: Activity? = null
    private val callback = object : Application.ActivityLifecycleCallbacks {
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            if (currentActivity == null) {
                currentActivity = activity
            }
        }

        override fun onActivityStarted(activity: Activity) {
        }

        override fun onActivityResumed(activity: Activity) {
            currentActivity = activity
        }

        override fun onActivityPaused(activity: Activity) {
        }

        override fun onActivityStopped(activity: Activity) {
        }

        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
        }

        override fun onActivityDestroyed(activity: Activity) {
            if (currentActivity == activity) {
                currentActivity = null
            }
        }
    }

    init {
        application.registerActivityLifecycleCallbacks(callback)
    }

    override fun pop() {
        val activity = currentActivity ?: return
        if (activity is ComposeHostActivity) {
            if (activity.navController?.popBackStack() != true) {
                activity.finish()
            }
        } else {
            activity.finish()
        }
    }

    override fun factory(storage: SchemeDefStorage, batch: Boolean): SchemeTransaction {
        val activity = currentActivity ?: throw RuntimeException("current activity is null")
        if (batch) {
            return SchemeExecBatch(activity, storage, transitionProvider)
        }
        return SchemeExecSingle(activity, storage, transitionProvider)
    }

    protected fun finalize() {
        application.unregisterActivityLifecycleCallbacks(callback)
        currentActivity = null
    }
}
